package fctuc.dei.myTwitter.client;

import static fctuc.dei.myTwitter.constants.MyTwitterConstants.MAX_ATTEMPTS;
import static fctuc.dei.myTwitter.constants.MyTwitterConstants.sleep;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import fctuc.dei.myTwitter.com.MT;
import fctuc.dei.myTwitter.com.Message;
import fctuc.dei.myTwitter.com.Serializer;


class NewNioConnection extends ServerConnection {

	public Socket socket;
	private DataInputStream in;
	private DataOutputStream out;
	private Object mutex = new Object(); //bloqueia a thread enquanto espera
	public BlockingQueue<Message> queue;
	

	public NewNioConnection(MyTwitterClient c){
		super(c);
		this.queue = new LinkedBlockingQueue<Message>();
		new NetworkIn(this).start();
		new NetworkOut(this.queue).start();
	}

	@Override
	public void realConnect() {

		for(int i = 0; i < MAX_ATTEMPTS; i++){
			boolean sucess = false;
			try{
				if(this.connected){
					sucess = true;
					return; //se ja esta ligado sai
				}

				if(disconnect) //se desligou voluntariamente sai
					return;

				//estabelece a licacao (TCP)
				if(this.socket != null)
					this.socket.close(); //just to be sure
				
				this.socket = new Socket(client.host, client.listenerPort);
				in = new DataInputStream(socket.getInputStream());
				out = new DataOutputStream(socket.getOutputStream());
				
				sucess = true;
				connected = true;
				this.setServerStatus(connected);
				
				if(!client.firstConnection){
					this.client.initiateConnection();
				}
				
				this.repeatAll();
				return; //consegui ligar-me
			}catch (Exception e){

			}finally {
				
				if(disconnect){
					sucess = true;
					return; //disconnect foi intencional
				}
				else if (!sucess){
					this.setServerStatus(false);
					try{
						if(this.socket != null)
							this.socket.close(); //primeiro fecho o socket
					}catch (IOException io){
						io.printStackTrace();
					}
					connected = false;
					//espera alguns segundos ate voltar a tentar
					System.out.print("\nConnection Error! Reconnecting.");
					sleep(2000);
				}
				else{
					synchronized(mutex){
						mutex.notifyAll(); //avisa a malta que ja estamos ligados outra vez
					}
				}
			}//finally
		}//for
		//se nao consegui ligar ate aqui: desiste
		if(!disconnect)
			client.com.write("Nao foi possivel ligar ao servidor!\n\nA terminar...");
		terminate();

	}

	@Override
	public boolean realSend(Message m) {

		if(!connected)
			return false;
		
		try {
			this.queue.put(m);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

//		try {
//			out.write(Serializer.serializeObject(m).array());
//			out.flush();
//		} catch (IOException e) {
//			System.out.println("Error writting to OutputStream");
//			return false;
//		}

		return true;
	}

	@Override
	public void realTerminate() {
		//terminate = true;
		/*
		synchronized (mutex){
			mutex.notifyAll();
			try {
				if(this.socket != null)
					this.socket.close();
			} catch (IOException e) {
				System.out.println("Error closing socket: "+e.getMessage());
			}
		}*/
		disconnect = true;
		connected = false;
	}
	
	private class NetworkOut extends Thread{
		
		BlockingQueue<Message> outbox;
		public NetworkOut(BlockingQueue<Message> c){
			this.outbox = c;
		}
		
		public void run(){
			while(!disconnect){
//			while(true){
				try {
					//TODO
					Message m = outbox.take();
					out.write(Serializer.serializeObject(m).array());
					out.flush();
				} catch (Exception e) {
//					System.out.println("Error writting to OutputStream");
					synchronized (mutex){
						mutex.notifyAll();
						try {
							if(socket != null)
								socket.close();
						} catch (IOException ex) {
							System.out.println("Error closing socket: "+ex.getMessage());
						}
					}
				}
			}
			synchronized (mutex){
				mutex.notifyAll();
				try {
					if(socket != null)
						socket.close();
				} catch (IOException ex) {
					System.out.println("Error closing socket: "+ex.getMessage());
				}
			}
		}
	}
	
	private class NetworkIn extends Thread{
		
		NewNioConnection conn;
		NetworkIn(NewNioConnection sc){
			this.conn = sc;
		}
		
		public void run(){
			while(true){
				synchronized (mutex){
					while(!connected || disconnect){
						try {
							mutex.wait();
						} catch (InterruptedException e) {
							//e.printStackTrace();
							System.out.println("Erro na ligacao - InterruptedException");
						}
					}
				}

				while(true){
					
					try {
						//receive( (Message) in.readObject());
						int objSize = in.readInt();
						byte[] serializedObject = new byte[objSize];
						in.readFully(serializedObject);
						Object obj = Serializer.deserializeObject(ByteBuffer.wrap(serializedObject), objSize);
						//client.inbox.put(new Envelope((Message) obj,conn));
						client.handleMessage((Message) obj, conn);
					} catch (IOException e) {
						connect();
					} catch (ClassNotFoundException e) {
						connect();
					} 
//					catch (InterruptedException e) {
//						connect();
//					}
				}
			}
		}
	}

	@Override
	public void noConnectionWarning() {
		client.com.write("WARNING: no connection > command discarded\n");
	}
	@Override
	public boolean realSend(Message m, SelectionKey key) {
		// TODO Auto-generated method stub
		return false;
	}
}


